{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Contrarian view on `map` and `filter`.\n",
    "Although [PEP 8](https://www.python.org/dev/peps/pep-0008/) is silent on the topic, it's become recommended in many Python circles to eschew `map` and `filter` in favor of generator expressions or list comprehensions.  For example, this [Stack Overflow question](http://stackoverflow.com/questions/5426754/google-python-style-guide) received and accepted a typical response.  Ironically, that question misquoted the [google style guide](https://google.github.io/styleguide/pyguide.html?showone=Deprecated_Language_Features#Deprecated_Language_Features), which this author happens to agree with.\n",
    "\n",
    "> Use list comprehensions and for loops instead of filter and map *when the function argument would have been an inlined lambda anyway*. [emphasis added]\n",
    "\n",
    "The style guide also shows a non-lambda version as a positive example:\n",
    "> `map(math.sqrt, data)`    # Ok. No inlined lambda expression.\n",
    "\n",
    "First, a brief history of how the Python community arrived at this state.\n",
    "\n",
    "## Python 1\n",
    "Prior to version 2.0, Python had neither list comprehensions nor nested scopes.  Therefore simple `map` and `filter` operations had to use a `for... append` loop, or `lambda`.  But the lacked of nested scopes was inherently crippling to the latter approach.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "<map at 0x108e57250>"
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = 2\n",
    "map(lambda y: y * x, range(5))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A `NameError` would have been raised on `x`, because it's not defined in the inner scope.  One clever work-around was to shadow default arguments."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "[0, 2, 4, 6, 8]"
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = 2\n",
    "list(map(lambda y, x=x: y * x, range(5)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Unsurprisingly, that was widely viewed as an ugly hack.  Many resigned themselves to `for... append` loops instead.\n",
    "\n",
    "## Python 2\n",
    "Then Python added list comprehensions in 2.0, and that became the [one obvious way](https://en.wikipedia.org/wiki/Zen_of_Python) to do it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "[0, 2, 4, 6, 8]"
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = 2\n",
    "[y * x for y in range(5)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Python acquired nested scopes in the next version, 2.1, but the damage was done.  Functional programming in Python in general, and `lambda` in particular, was widely frowned upon.  Even though the lack of nested scopes affected all inner functions used in any context; it was never really about `lambda` per se.\n",
    "\n",
    "## Python 3\n",
    "It's sometimes claimed to this day that `map` and `filter` only exist for backwards compatibility.  But that belies the history of Python 3.  `map`, `filter`, and `reduce` were all considered for removal.  But only `reduce` was banished to the `functools` module.  `map` and `filter` were not only retained, but updated to return iterators.\n",
    "\n",
    "So it's already dubious to claim that using a built-in is unapproved.  But the real point is that `map` and `filter` remain a higher level abstraction.  Sure, with `lambda` there are the same number of logical components, and it's just a matter of syntactic sugar.  But there is some abstraction value when the functions already have a name.\n",
    "\n",
    "It's also commonly pointed out that generator expressions are superior because they can do a `map` and `filter` simultaneously, but crucially only if the `filter` comes first.  Consider this task: normalizing an iterable of strings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "['sample']"
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "values = 'sample ', ' '\n",
    "list(filter(None, map(str.strip, values)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note `list` is only being used for printing, and should be ignored for the sake of comparisons.\n",
    "\n",
    "As for the alternative, surely calling `strip` twice to use a single expression is just plain cheating.  So really the only option is:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "['sample']"
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[value for value in (value.strip() for value in values) if value]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Some would consider nested comprehensions already enough to separate with a temporary name.  But that's inadvertently acknowledging how much more verbose it is.\n",
    "\n",
    "Can it really be claimed that the latter is more readable than the former?  It's just boilerplate, which never seems acknowledged in small examples.  But if one only has to double the size of the context to show how verbose comprehensions are, doesn't that demonstrate the value of `map` and `filter`.\n",
    "\n",
    "## Epilogue\n",
    "And now a shameless plug of the author's [placeholder](https://pypi.org/project/placeholder/) package for readers who  appreciate function-style programming.  It provides syntactic sugar for `lambda`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "[0, 2, 4, 6, 8]"
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from placeholder import _\n",
    "\n",
    "list(map(_ * 2, range(5)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But even speaking as the author, `map` isn't the best use case.  Sort keys are a much better example, since there is no competing syntax."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "'ba'"
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "min(['ab', 'ba'], key=_[-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Python 3.8's new [assignment expressions](https://docs.python.org/3/whatsnew/3.8.html#assignment-expressions) provide yet another alternative."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "env": {},
   "interrupt_mode": "signal",
   "language": "python",
   "metadata": {},
   "name": "python3"
  },
  "nikola": {
   "category": "style",
   "date": "2018-03-31",
   "description": "",
   "link": "",
   "slug": "map-and-filter",
   "tags": "",
   "title": "Map and Filter",
   "type": "text"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}